# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: inkyu

import datetime
import logging
import json
from typing import List, Optional, Union

import requests
from tornado import gen

from coin.base.param_util import to_list
from coin.base.timestamp import get_timestamp
from coin.exchange.util.async_http_client import AsyncHttpClient
import coin.flow.topic_map as topic_map


class _BinanceBookQueryContext(object):
  def __init__(self,
               ioloop,
               publisher,
               logger,
               symbol,
               period,
               *,
               http_client=None,
               max_connections=2):
    self._ioloop = ioloop
    self._publisher = publisher
    self._logger = logger
    self._symbol = symbol
    self._period = period

    self._stopped = False
    self._querying = False
    self._http_client = http_client or AsyncHttpClient(max_connections=max_connections)

  def stop(self):
    self._stopped = True

  def start(self):
    if self._stopped:
      return
    self._ioloop.add_callback(self._run_query)
    self._ioloop.add_timeout(self._period, self._on_timeout)

  def _on_timeout(self):
    if self._stopped:
      return
    if not self._querying:
      self._ioloop.add_callback(self._run_query)
    else:
      self._logger.warning('Still qeurying. Skip querying this time')
    self._ioloop.add_timeout(self._period, self._on_timeout)

  def _on_response(self, symbol, timestamp, response):
    if response.status_code != 200:
      self._logger.warn('Response: %d %s', response.status_code, response.content)
      return

    data = json.loads(response.content)
    message = {'symbol': symbol, 'data': data}
    self._publisher.write(timestamp, message)

  @gen.coroutine
  def _run_query(self):
    self._querying = True
    self._logger.info('Start querying %d symbols' % len(self._symbol))
    try:
      for symbol in self._symbol:
        response = None
        for try_n in range(0, 3):  # Try 3 times on error
          if self._stopped:
            return

          # 1000: 50 points, 100: 1 point
          url = '%s&symbol=%s' % (self._url, symbol)
          try:
            response = yield self._http_client.get(url)
          except requests.exceptions.RequestException:
            self._logger.exception('RequestException')
            continue
          timestamp = get_timestamp()
          if response.status_code != 200:
            self._logger.exception('Query error %s: %s' % (symbol, response))
            continue
          break

        if self._stopped:
          return

        try:
          if response is not None:
            self._on_response(symbol, timestamp, response)
          else:
            self._logger.error('Retry failure')
        except Exception:
          self._logger.exception('Exception on procession a response: %s' % symbol)
    finally:
      self._querying = False
      if not self._stopped:
        self._logger.info('Query done')
      else:
        self._logger.info('Stopped')

  def register_url(self, url):
    self._url = url


class BinanceBookQueryerBase(object):
  def __init__(self,
               queue,
               worker_id: str,
               symbol: Union[List[str], str],
               period=None,
               logger=None):
    self._logger = logger or logging.getLogger(__name__)
    self._symbol = to_list(symbol)
    self._period = period or datetime.timedelta(minutes=10)

    self._context = None
    self._publisher = None
    self._init_publisher(queue, worker_id)

  def __del__(self):
    self.stop()
    self._publisher.close()

  def _init_publisher(self, queue, worker_id):
    topic_id = topic_map.search_topic_id('%s_%s' % (self._topic, worker_id))[0][1]
    self._publisher = queue.get_writer(topic_id=topic_id)

  def start(self, ioloop, later_td: Optional[datetime.timedelta] = None):
    assert self._context is None
    self._context = _BinanceBookQueryContext(ioloop,
                                             self._publisher,
                                             self._logger,
                                             self._symbol,
                                             self._period)
    self._context.register_url(self._url)

    if later_td is None:
      self._context.start()
    else:
      ioloop.add_timeout(later_td, self._context.start)

  def stop(self):
    if self._context is not None:
      self._context.stop()
      self._context = None


class BinanceBookQueryer(BinanceBookQueryerBase):
  def __init__(self,
               queue,
               worker_id: str,
               symbol: Union[List[str], str],
               period=None,
               logger=None):
    self._topic = 'binance_rest_depth'
    self._url = 'https://www.binance.com/api/v1/depth?limit=100'
    super().__init__(queue, worker_id, symbol, period, logger)
